using System;
using System.Collections;
using System.ComponentModel;
using System.Data.SqlTypes;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using PasswordGenerator;
using RedGate.SQLCompare.Engine;
using RedGate.SQLDataGenerator.Engine.Generators;
using RedGate.SQLDataGenerator.Engine.Generators.Static;
using RedGate.SQLDataGenerator.Engine.Generators.Support;

namespace PasswordGenerator
{
    [Generator(typeof(byte[]), "Business", "Password generator", "generates passwords")]
    //Old Strings
    [SupportSqlType(SqlType.Text)]
    [SupportSqlType(SqlType.Varchar)]
    [SupportSqlType(SqlType.Char)]

    //Unicode Strings
    [SupportSqlType(SqlType.NText)]
    [SupportSqlType(SqlType.NVarchar)]
    [SupportSqlType(SqlType.NChar)]

    [SupportSqlType(SqlType.Binary)]
    [SupportSqlType(SqlType.Image)]
    [SupportSqlType(SqlType.Variant)]
    [SupportSqlType(SqlType.Varbinary)]

    public class PasswordGenerator : GeneratorBase, IGeneratorUIStyle, IGeneratorUI
    {
        private PasswordGeneratorUI m_UI;
        
        private string m_SaltColumn = String.Empty;
        private string m_PasswordTextColumn = String.Empty;
        private EncryptionType m_EncryptionType = EncryptionType.SHA512;
        private SaltCombiningMethod m_SaltCombiningMethod = SaltCombiningMethod.AppendText;
        private PasswordTextEncoding m_PasswordTextEncoding = PasswordTextEncoding.UNICODE;
        
        public string SaltColumn
        {
            get { return m_SaltColumn; }
            set { m_SaltColumn = value; }
        }

        public string PasswordTextColumn
        {
            get { return m_PasswordTextColumn; }
            set { m_PasswordTextColumn = value; }
        }

        public EncryptionType EncryptionType
        {
            get { return m_EncryptionType; }
            set { m_EncryptionType = value; }
        }

        public SaltCombiningMethod SaltCombiningMethod
        {
            get { return m_SaltCombiningMethod; }
            set { m_SaltCombiningMethod = value; }
        }

        public PasswordTextEncoding PasswordTextEncoding
        {
            get { return m_PasswordTextEncoding; }
            set { m_PasswordTextEncoding = value; }
        }

        public PasswordGenerator(GeneratorParameters parameters) : base(parameters)
        {
        }

        public UIGeneratorStyle UIGeneratorStyle
        {
            get { return UIGeneratorStyle.UIPartial; }
        }

        #region IGeneratorUI Members
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public UserControl UserControl
        {
            get
            {
                if (m_UI == null)
                    m_UI = new PasswordGeneratorUI(this, m_Parameters);
                return m_UI;
            }
            set
            {
                m_UI = null;
            }
        }

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IGeneratorSimpleUI IGeneratorSimpleUI
        {
            get { return UserControl as IGeneratorSimpleUI; }
            set { m_UI = null; }
        }
        #endregion

        public override IEnumerator GetNonNullEnumerator(GenerationSession session)
        {
            bool isTextColumn = false;

            SqlType sqlType = m_Parameters.GetSqlType();

            if(sqlType == SqlType.Text || sqlType == SqlType.Varchar || 
               sqlType == SqlType.Char || sqlType == SqlType.NText || 
               sqlType == SqlType.NVarchar || sqlType == SqlType.NChar)
            {
                isTextColumn = true;
            }


            int saltIndex = -1;
            try
            {
                if (m_SaltColumn != null && m_SaltColumn.Length > 0)
                {
                    saltIndex = session.OutputRow.GetOrdinal(m_SaltColumn);
                }
            }
            catch
            {
                saltIndex = -1;
            }

            int passwordIndex = -1;
            try
            {
                if (m_PasswordTextColumn != null && m_PasswordTextColumn.Length != 0)
                {
                    passwordIndex = session.OutputRow.GetOrdinal(m_PasswordTextColumn);
                }
            }
            catch
            {
                passwordIndex = -1;
            }

            Random r = new Random(m_Seed);

            HashAlgorithm hashAlgorithm;

            switch(m_EncryptionType)
            {
                case EncryptionType.MD5:
                    hashAlgorithm = new MD5CryptoServiceProvider();
                    break;
                case EncryptionType.SHA1:
                    hashAlgorithm = new SHA1Managed();
                    break;
                case EncryptionType.SHA256:
                    hashAlgorithm = new SHA256Managed();
                    break;
                case EncryptionType.SHA384:
                    hashAlgorithm = new SHA384Managed();
                    break;
                case EncryptionType.SHA512:
                    hashAlgorithm = new SHA512Managed();
                    break;
                default:
                    throw new ArgumentException("Unrecognized EncryptionType");
            }

            while(true)
            {
                byte [] salt;
                if(saltIndex >= 0)
                {
                    salt = GetBytes(session, saltIndex);
                }
                else
                {
                    salt = new byte[] {};
                }

                byte[] password;
                if (passwordIndex >= 0)
                {
                    password = GetBytes(session, passwordIndex);
                }
                else
                {
                    password = Encoding.Unicode.GetBytes(r.Next().ToString());
                }

                byte[] buffer;
                if(salt.Length >= 0)
                {
                    buffer = new byte[password.Length + salt.Length];
                    switch(m_SaltCombiningMethod)
                    {
                        case SaltCombiningMethod.AppendText:
                            Array.Copy(password, 0, buffer, 0, password.Length);
                            Array.Copy(salt, 0, buffer, password.Length, salt.Length);
                            break;
                        case SaltCombiningMethod.PrependText:
                            Array.Copy(salt, 0, buffer, 0, salt.Length);
                            Array.Copy(password, 0, buffer, salt.Length, password.Length);                            
                            break;
                        default:
                            throw new ArgumentException("Invalid SaltCombiningMethod");
                    }
                }
                else
                {
                    buffer = password;
                }

                byte [] result = hashAlgorithm.ComputeHash(buffer);

                if (isTextColumn)
                {
                    yield return Convert.ToBase64String(result);
                }
                else
                {
                    yield return result;
                }

            }
            
        }

        private byte [] GetBytes(GenerationSession session, int columnIndex)
        {
            SqlType sqlType = m_Parameters.Table.Fields[columnIndex].SqlType;

            if(sqlType == SqlType.Binary || sqlType == SqlType.Varbinary || sqlType == SqlType.Image)
            {
                object val = session.OutputRow[columnIndex];

                if(val is SqlBinary)
                {
                    return ((SqlBinary) val).Value;
                }
                else if (val is SqlBytes)
                {
                    return ((SqlBytes)val).Value;
                }
                else if (val is byte[])
                {
                    return ((byte[])val);
                }
            }

            switch(m_PasswordTextEncoding)
            {
                case PasswordTextEncoding.UTF7:
                    return Encoding.UTF7.GetBytes(session.OutputRow.GetString(columnIndex));
                case PasswordTextEncoding.UTF8:
                    return Encoding.UTF8.GetBytes(session.OutputRow.GetString(columnIndex));
                case PasswordTextEncoding.UNICODE:
                    return Encoding.Unicode.GetBytes(session.OutputRow.GetString(columnIndex));
                case PasswordTextEncoding.BigEndianUnicode:
                    return Encoding.BigEndianUnicode.GetBytes(session.OutputRow.GetString(columnIndex));
                case PasswordTextEncoding.UTF32:
                    return Encoding.UTF32.GetBytes(session.OutputRow.GetString(columnIndex));
                case PasswordTextEncoding.ASCII:
                    return Encoding.ASCII.GetBytes(session.OutputRow.GetString(columnIndex));
            }

            return Encoding.Unicode.GetBytes(session.OutputRow.GetString(columnIndex));
        }
    }
}